<!--
 * @Author: zhi1hw hanwei@zhi1tech.com
 * @Date: 2024-07-06 13:51:13
 * @LastEditors: zhi1hw hanwei@zhi1tech.com
 * @LastEditTime: 2025-01-04 00:46:43
 * @FilePath: \code\src\views\common\soundRecording.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <div
    class="voice-input"
    @touchstart="touchstart"
    @touchend="touchend"
    @touchmove="touchmove"
  >
    <!-- <audio id="audioPlayer" controls></audio> -->

    <div>
      {{
        state.isSoundRecording
          ? props.stateSound
            ? language.messageEN.send
            : language.messageZH.send
          : props.stateSound
          ? language.messageEN.talk
          : language.messageZH.talk
      }}
    </div>
    <span>
      <img
        class="other-yuyin"
        v-if="!state.isDisabled"
        src="@/assets/images/keyboard-icon.png"
        alt=""
        @click="closeVoiceButton"
      />
    </span>

    <van-loading v-if="state.loading" class="fullscreen-loading" />
  </div>
  <van-popup
    v-model:show="state.showBottom"
    round
    position="bottom"
    class="modal-class"
    safe-area-inset-bottom
    :style="{ height: '30%' }"
    @close="closeVoice"
  >
    <div class="sound-recording">
      <div class="sound-recording-box">
        <p>
          {{
            props.stateSound
              ? language.messageEN.listen
              : language.messageZH.listen
          }}
        </p>
        <p>
          {{
            state.isSoundRecording
              ? props.stateSound
                ? language.messageEN.release
                : language.messageZH.release
              : props.stateSound
              ? language.messageEN.swipe
              : language.messageZH.swipe
          }}
        </p>
      </div>
      <div
        class="loaderbg loader"
        :class="!state.blackBoxSpeak ? 'loaderbg1' : ''"
        ref="loaderRef"
      >
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
      </div>
      <!-- <van-popup
      v-model:show="state.blackBoxSpeak"
      :style="{ padding: '64px' }"
      v-if="state.btRecoding"
      catchtouchmove="ture"
      >手指上划取消录制</van-popup
    > -->
      <!-- <van-popup
        v-model:show="state.isSoundRecording"
        :style="{ padding: '64px' }"
        >松开手指，取消录制</van-popup
      > -->
    </div>
  </van-popup>
</template>
  <script setup lang="ts">
import {
  ComponentInternalInstance,
  getCurrentInstance,
  inject,
  onMounted,
  onUnmounted,
  reactive,
  watch,
  defineProps,
  provide,
  ref,
} from "vue";
import { prop } from "vue-class-component";
import language from "@/lang/zh";
import { showDialog } from "vant";

const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emits = defineEmits(["addVoiceItem", "bindChangeKeyword"]);
const themeColor: any = inject("themeColor");
const show = ref(false);

const state = reactive({
  loop: "",
  btRecoding: false,
  blackBoxSpeak: true,
  startY: 0,
  timeOutEvent: 0,
  isSoundRecording: false,
  showBottom: false,
  mediaRecorder: null,
  audioChunks: [],
  loading: false
});
const props = defineProps({
  stateSound: {
    type: Boolean,
    default: "",
  },
  stream: {
    type: Object,
    default: {},
  },
});

const loaderRef = ref(null);
const closeVoiceButton = () => {
  emits("bindChangeKeyword");
  state.showBottom = false;
  if (state.mediaRecorder) {
    state.mediaRecorder.stop();
  }
};
const closeVoice = () => {
  state.showBottom = false;
  if (state.mediaRecorder) {
    state.mediaRecorder.stop();
  }
};
const blobToBase64 = (blob, callback) => {
  const reader = new FileReader();

  // 文件读取成功后的回调
  reader.onloadend = function () {
    // reader.result 包含了 Base64 编码的字符串
    callback(reader.result);
  };

  // 开始读取 Blob 数据
  reader.readAsDataURL(blob);
};
// 手指开始触发
const touchstart = (e: any) => {
  state.startY = e.targetTouches[0].clientY;
  state.timeOutEvent = 0;
  // 长按1000毫秒后执行
  state.timeOutEvent = setTimeout(async () => {
    try {
      startRecord();
    } catch (error) {
      alert(error);
    }
  }, 500);
  return false;
};

const startRecord = async () => {
  // const audioPlayer = document.getElementById("audioPlayer");
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  openVoiceModal(stream);
};
const audioContext = new AudioContext();

const bufferToWave = (audioBuffer, len) => {
  const numOfChan = audioBuffer.numberOfChannels;
  const length = len * numOfChan * 2 + 44;
  const buffer = new ArrayBuffer(length);
  const view = new DataView(buffer);
  const channels = [];
  let offset = 0;
  let pos = 0;

  function writeString(str) {
    for (let i = 0; i < str.length; i++) {
      view.setUint8(offset++, str.charCodeAt(i));
    }
  }

  // 写 WAV 文件头
  writeString("RIFF");
  view.setUint32(offset, length - 8, true);
  offset += 4;
  writeString("WAVE");
  writeString("fmt ");
  view.setUint32(offset, 16, true);
  offset += 4;
  view.setUint16(offset, 1, true); // PCM
  offset += 2;
  view.setUint16(offset, numOfChan, true);
  offset += 2;
  view.setUint32(offset, audioBuffer.sampleRate, true); // 采样率
  offset += 4;
  view.setUint32(offset, audioBuffer.sampleRate * numOfChan * 2, true); // 字节率
  offset += 4;
  view.setUint16(offset, numOfChan * 2, true); // 块对齐
  offset += 2;
  view.setUint16(offset, 16, true); // 量化位数
  offset += 2;
  writeString("data");
  view.setUint32(offset, len, true);
  offset += 4;

  // 写音频数据
  for (let i = 0; i < numOfChan; i++) {
    channels.push(audioBuffer.getChannelData(i));
  }

  while (pos < len) {
    for (let i = 0; i < numOfChan; i++) {
      const sample = channels[i][pos] * 0x7fff;
      view.setInt16(
        offset,
        sample < 0 ? sample : sample > 0x7fff ? 0x7fff : sample,
        true
      );
      offset += 2;
    }
    pos++;
  }

  return new Blob([view], { type: "audio/wav" });
};
const openVoiceModal = (stream: any) => {
  state.showBottom = true;
  state.isSoundRecording = true;

  state.mediaRecorder = new MediaRecorder(stream);
  // 录制数据时的回调
  state.mediaRecorder.ondataavailable = (event: any) => {
    state.audioChunks.push(event.data);
  };
  // 录音停止时的回调
  state.mediaRecorder.onstop = async () => {
    state.loading = true
    // 合并音频块并生成 Blob
    const audioBlob = new Blob(state.audioChunks, {
      type: "audio/wav",
    });

    // 将 Blob 数据转成 AudioBuffer
    const arrayBuffer = await audioBlob.arrayBuffer();
    const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

    // 设定目标采样率（例如: 22050 Hz）
    const targetSampleRate = 16000;

    // 创建一个新的 AudioBufferSourceNode
    const offlineAudioContext = new OfflineAudioContext(
      audioBuffer.numberOfChannels,
      audioBuffer.length,
      targetSampleRate
    );

    // 创建一个 BufferSourceNode 并连接到 OfflineAudioContext
    const source = offlineAudioContext.createBufferSource();
    source.buffer = audioBuffer;
    source.connect(offlineAudioContext.destination);

    // 开始播放音频，触发渲染
    source.start();

    // 渲染音频并获得处理后的数据
    offlineAudioContext
      .startRendering()
      .then((renderedBuffer) => {
        // 将处理后的 AudioBuffer 转回 Blob
        const wavBlob = bufferToWave(renderedBuffer, renderedBuffer.length);
        const audioFile = new File(
          [wavBlob],
          "audio_with_different_sample_rate.wav",
          { type: "audio/wav" }
        );

        // 创建下载链接
        // const downloadLink = document.createElement("a");
        // downloadLink.href = URL.createObjectURL(file);
        // downloadLink.download = "audio_with_different_sample_rate.wav";
        // downloadLink.click();

        // const audioFile = new File([audioBlob], "audio.wav", {
        //   type: "audio/wav",
        // });

        const formData = new FormData();
        formData.append("file", audioFile);

        state.audioChunks = []; // 重置音频数据
        if (state.blackBoxSpeak) {
          proxy?.$api
          .post(
            process.env.VUE_APP_BASE_API_URL + "/wechatApi/quizAudioAsr",
            formData,
            {
              headers: {
                "Content-Type": "multipart/form-data", // 不要手动设置这个
              },
            }
          )
          .then((res: any) => {
            if (res.code === 200) {
              addVoiceItems(JSON.parse(res.data));
              state.loading = false

             
            } else {
              showDialog({ message: "文字识别失败" });
              state.loading = false

            }
          });
        } else {
          state.blackBoxSpeak = true;
          state.loading = false
        }
       
      })
      .catch((error) => {
        console.error("Error rendering audio:", error);
      });

    // const audioFile = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });

    // const formData = new FormData();
    // formData.append('file', audioFile);

    // state.audioChunks = []; // 重置音频数据

    // proxy?.$api
    // .post(process.env.VUE_APP_BASE_API_URL + "/wechatApi/quizAudioAsr", formData, {
    //   headers: {
    //     'Content-Type': 'multipart/form-data',  // 不要手动设置这个
    //   },
    // })
    // .then((res: any) => {
    //   if (res.code === 200) {

    //   }
    // });
    // 创建下载链接
    // downloadLink.href = audioUrl;
    // downloadLink.style.display = "block"; // 显示下载链接
  };

  state.mediaRecorder.start();
};

// 短按
const startRecordClick = () => {
  // 长按没有超过1000毫秒
};
// 手指离开屏幕触发
const touchend = () => {
  clearTimeout(state.timeOutEvent);
  if (state.timeOutEvent == 0) {
    state.btRecoding = true;
    startRecordClick();
  } else {
    state.btRecoding = false;
    state.isSoundRecording = false;
    stopRecord();
  }
};
// 滑动触发
const touchmove = (e: any) => {
  const endY = e.targetTouches[0].clientY;
  if (state.startY - 30 < endY) {
    state.blackBoxSpeak = true;
  } else {
    state.blackBoxSpeak = false;
  }
};

const stopRecord = () => {
  state.showBottom = false;
  if (state.mediaRecorder) {
    state.mediaRecorder.stop();
    
  }

  state.isSoundRecording = false;
};
// 通知父元素插入问答数据
const addVoiceItems = (data:any) => {
  if(data.sentences.length > 0) {
    let obj = {
      text: data.sentences[0].text || '',
      type: 0,
      itemType: "voice",
    };
    emits("addVoiceItem", obj);
    emits("bindChangeKeyword");
  } else {
    showDialog({ message: "文字识别失败" });

  }
 
};

///audioASR?sid=3sh9I84Pq2zosYY9QzjQfrmaBLaER_E8_2LnuZpiNqBSePksAaAXeIxycOk4P9ybh 阿里语音识别接口
// /getSessionQAList?sessionId=8EC36CCB-4E18-4A54-881B-B2CD00743E17 获取会话问答数据接口
onMounted(() => {});
</script>
  
<style lang="scss" scoped>
.fullscreen-loading {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.8);
  z-index: 9999;
}
.area-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: v-bind("themeColor.textColorLighten");
  font-size: 24px;
  img {
    width: 86px;
    margin-bottom: 10px;
    border: 1px dashed v-bind("themeColor.borderColorLighten");
    pointer-events: none;
  }
}
.voice-input {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
  div {
    font-size: 32px;
    font-weight: 700;
    letter-spacing: 0px;
    line-height: 44.8px;
    color: rgba(40, 51, 74, 1);
    text-align: left;
    vertical-align: top;
    margin: 0 auto;
  }
  img {
    display: flex;
    width: 60px;
  }
}
.sound-recording-box {
  p {
    display: block;
    text-align: center;
  }
  & > p:first-child {
    font-size: 32px;
    font-weight: 700;
    letter-spacing: 0px;
    line-height: 44.8px;
    color: rgba(40, 51, 74, 1);
    text-align: center;
    vertical-align: top;
    margin: 68px 0 48px 0;
  }
  & > p:last-child {
    font-size: 28px;
    font-weight: 500;
    letter-spacing: 0px;
    line-height: 58px;
    color: rgba(148, 162, 176, 1);
    text-align: center;
    vertical-align: top;
  }
}
.modal-class {
  padding: 0 30px;
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
  .loader,
  .loaderbg {
    margin: auto;
    width: calc(100% - 60px);
    height: 96px;
    display: flex;
    justify-content: center;
    opacity: 1;
    border-radius: 10px;
    background: rgba(92, 178, 255, 0.1);
    align-items: center;
    position: absolute;
    bottom: 100px;
  }

  .loader span,
  .loaderbg span {
    display: block;
    background: rgba(92, 178, 255, 1);
    width: 7px;
    height: 10%;
    border-radius: 14px;
    margin-right: 14px;
    float: left;
    margin-top: 0;
  }
  .loaderbg1 {
    background: rgba(237, 28, 36, 0.1);
    span {
      background: rgba(237, 28, 36, 1);
    }
  }
  .loader span:last-child {
    margin-right: 0px;
  }
  .loader span:nth-child(1) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.4s infinite linear;
  }
  .loader span:nth-child(2) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.2s infinite linear;
  }
  .loader span:nth-child(3) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1s infinite linear;
  }
  .loader span:nth-child(4) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.8s infinite linear;
  }
  .loader span:nth-child(5) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.6s infinite linear;
  }
  .loader span:nth-child(6) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.4s infinite linear;
  }
  .loader span:nth-child(7) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.2s infinite linear;
  }
  .loader span:nth-child(8) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0s infinite linear;
  }
  .loader span:nth-child(9) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.2s infinite linear;
  }
  .loader span:nth-child(10) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.4s infinite linear;
  }
  .loader span:nth-child(11) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.6s infinite linear;
  }
  .loader span:nth-child(12) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.8s infinite linear;
  }
  .loader span:nth-child(13) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1s infinite linear;
  }
  .loader span:nth-child(14) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.2s infinite linear;
  }
  .loader span:nth-child(15) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.4s infinite linear;
  }
  .loader span:nth-child(16) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.4s infinite linear;
  }
  .loader span:nth-child(17) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.2s infinite linear;
  }
  .loader span:nth-child(18) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1s infinite linear;
  }
  .loader span:nth-child(19) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.8s infinite linear;
  }
  .loader span:nth-child(20) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.6s infinite linear;
  }
  .loader span:nth-child(21) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.4s infinite linear;
  }
  .loader span:nth-child(22) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.2s infinite linear;
  }
  .loader span:nth-child(23) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0s infinite linear;
  }
  .loader span:nth-child(24) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.2s infinite linear;
  }
  .loader span:nth-child(25) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.4s infinite linear;
  }
  .loader span:nth-child(26) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.6s infinite linear;
  }
  .loader span:nth-child(27) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 0.8s infinite linear;
  }
  .loader span:nth-child(28) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1s infinite linear;
  }
  .loader span:nth-child(29) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.2s infinite linear;
  }
  .loader span:nth-child(30) {
    --her: 10%;
    --her1: 15%;
    animation: load 2s 1.4s infinite linear;
  }
  @keyframes load {
    0% {
      height: var(--her);
    }

    100% {
      height: var(--her1);
    }
  }
}
</style>